﻿/*
 * Created by SharpDevelop.
 * User: sanmadmin
 * Date: 6/29/2011
 * Time: 5:57 PM
 * 
 * To change this template use Tools | Options | Coding | Edit Standard Headers.
 */
using System;
using System.Collections.Generic;
using System.Net.Security;
using System.Net;
using System.Text;
using System.Reflection;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;

namespace RTMP
{
	/// <summary>
	/// Description of SharedObject.
	/// </summary>
    public enum SOEvents : int
    {
    	Use=1,				// Client --> Server inform creation of a named shared object
    	Release=2, 			// Client --> Server SO deleted client side
    	RequestChange=3,	// Client --> Server request of changing the value of a named parameter
    	Change=4,			// Server --> Client notify all clients (except requester) about changing the vale of a named parameter
    	Success=5,			// Server --> Client inform requester about accepting change of the parameter
    	SendMessage=6,		// Client --> Server broadcast a message
    						// Server --> Client broadcast a message to all clients(including originator)
    	Status=7,			// Server --> Client inform error status
    	Clear=8,			// Server --> Client clear shared object
    	Remove=9,			// Server --> Client delete a named parameter
    	RequestRemove=10,	// Client --> Client request of deleting a named parameter
    	UseSuccess=11		// Server --> Client successful connection
    };
	public class SharedObject
	{
		private NetConnection con;
		public string name="";
		public bool IsConnected=false;
		public bool IsPersistant=false;
		public Logger logger=new Logger();
		private object data;
		public object client=null;
		private Dictionary<object, object>PendingData=new Dictionary<object, object>();
		public delegate void SyncEventHandler(string name,object oldData,object newData);
		public delegate void ConnectEventHandler();
		public delegate void ClearEventHandler();
		public delegate void StatusEventHandler(string status,string message);
		public event SyncEventHandler Sync;
		public event ConnectEventHandler Connected;
		public event ClearEventHandler Cleared;
		public event StatusEventHandler Status;
		private bool initrunning=false;
		public SharedObject()
		{
		}
		public Dictionary<object, object> getData()
		{
			if(data==null)
				return null;
			IFormatter formatter = new BinaryFormatter();
	        Stream stream = new MemoryStream();
            formatter.Serialize(stream, data);
            stream.Seek(0, SeekOrigin.Begin);
            return (Dictionary<object, object>)formatter.Deserialize(stream);
			//return new Dictionary<object, object>((Dictionary<object, object>)data);
		}
		public bool setProperty(string property, object objValue)
		{
			if(PendingData.ContainsKey((object)property))
				return false;
			PendingData.Add((object)property,objValue);
			return true;
		}
		public void getRemote(NetConnection con,string name,bool bPersistent=false)
		{
			this.con=con;
			this.name=name;
			this.IsPersistant=bPersistent;
       		if(!con.SOList.ContainsKey(name))
       			con.SOList.Add(name,this);
       		
         	RTMPPacket packet=new RTMPPacket();
            packet.m_nChannel = 0x03;   // control channel (invoke)
        	packet.HeaderType=HeaderType.Large;
        	packet.PacketType= PacketType.SharedObject;
            packet.AllocPacket(4096);
	       	List<byte>enc=new List<byte>();
            short length = IPAddress.HostToNetworkOrder((short)name.Length);
            enc.AddRange(BitConverter.GetBytes(length));
            enc.AddRange(Encoding.ASCII.GetBytes(name));
        	Coding.EncodeInt32(enc,0,0);
        	if(IsPersistant)
        		Coding.EncodeInt32(enc,2,0);
        	else
        		Coding.EncodeInt32(enc,0,0);
        	Coding.EncodeInt32(enc,0,0);
        	enc.Add(0x01);
        	Coding.EncodeInt32(enc,0,0);
            Array.Copy(enc.ToArray(), packet.m_body, enc.Count);
            packet.m_nBodySize = (uint)enc.Count;
            if(con.SendPacket(packet))
            	logger.Log("SO connect request sent");
            else
            	logger.Log("SO connect request failed to send");
		}
		public void Close()
		{
       		if(!con.SOList.ContainsKey(name))
       			con.SOList.Add(name,this);
       		
         	RTMPPacket packet=new RTMPPacket();
            packet.m_nChannel = 0x03;   // control channel (invoke)
        	packet.HeaderType=HeaderType.Large;
        	packet.PacketType= PacketType.SharedObject;
            packet.AllocPacket(4096);
	       	List<byte>enc=new List<byte>();
            short length = IPAddress.HostToNetworkOrder((short)name.Length);
            enc.AddRange(BitConverter.GetBytes(length));
            enc.AddRange(Encoding.ASCII.GetBytes(name));
        	Coding.EncodeInt32(enc,0,0);
        	if(IsPersistant)
        		Coding.EncodeInt32(enc,2,0);
        	else
        		Coding.EncodeInt32(enc,0,0);
        	Coding.EncodeInt32(enc,0,0);
        	enc.Add(0x02);
        	Coding.EncodeInt32(enc,0,0);
            Array.Copy(enc.ToArray(), packet.m_body, enc.Count);
            packet.m_nBodySize = (uint)enc.Count;
            if(con.SendPacket(packet))
            	logger.Log("SO close request sent");
            else
            	logger.Log("SO close request failed to send");
		}
		public bool send(string message, object param)
		{
			try
			{
				List<byte>enc=new List<byte>();
				List<byte>temp=new List<byte>();
				encodeName(enc,name);
				Coding.EncodeInt32(enc,0x2);
				Coding.EncodeInt32(enc,0x0);
				Coding.EncodeInt32(enc,0x0);
				enc.Add(0x6); // client sends message event
				encodeString(temp,message);
				if(param.GetType()==typeof(bool))
					encodeBoolean(temp,(bool)param);
				else if(param.GetType()==typeof(double))
					encodeNumber(temp,(double)param);
				else if(param.GetType()==typeof(string))
					encodeString(temp,(string)param);
				else if(param.GetType()==typeof(List<object>) || param.GetType()==typeof(Dictionary<object,object>))
					encodeObject(ref temp,param);
				else
					return false;
				byte[] bl=BitConverter.GetBytes(temp.Count);
				enc.AddRange(new byte[]{bl[3],bl[2],bl[1],bl[0]});
				enc.AddRange(temp);
	         	RTMPPacket packet=new RTMPPacket();
	            packet.m_nChannel = 0x03;   // control channel (invoke)
	        	packet.HeaderType=HeaderType.Large;
	        	packet.PacketType= PacketType.SharedObject;
	            packet.AllocPacket(4096);
	            Array.Copy(enc.ToArray(), packet.m_body, enc.Count);
	            packet.m_nBodySize = (uint)enc.Count;
	            con.SendPacket(packet,true);
			}
			catch{return false;}
			return true;
		}
		public bool setDirty(string property)
		{
			if(data==null)
				return false;
			if(data.GetType()!=typeof(Dictionary<object, object>))
			   return false;
			try
			{
				List<byte>enc=new List<byte>();
				List<byte>temp=new List<byte>();
				encodeName(enc,name);
				Coding.EncodeInt32(enc,0x2);
				Coding.EncodeInt32(enc,0x0);
				Coding.EncodeInt32(enc,0x0);
				enc.Add(0x3); // client side named parameter changed event
				Dictionary<object, object>dic=(Dictionary<object, object>)this.data;
				object obj=null;
				if(!dic.TryGetValue((object)property,out obj))
					return false;
				if(obj==null)
					return false;
				encodeName(temp,property);
				if(obj.GetType()==typeof(bool))
					encodeBoolean(temp,(bool)obj);
				else if(obj.GetType()==typeof(double))
					encodeNumber(temp,(double)obj);
				else if(obj.GetType()==typeof(string))
					encodeString(temp,(string)obj);
				else if(obj.GetType()==typeof(List<object>) || obj.GetType()==typeof(Dictionary<object,object>))
					encodeObject(ref temp,obj);
				else
					return false;
				byte[] bl=BitConverter.GetBytes(temp.Count);
				enc.AddRange(new byte[]{bl[3],bl[2],bl[1],bl[0]});
				enc.AddRange(temp);
	         	RTMPPacket packet=new RTMPPacket();
	            packet.m_nChannel = 0x03;   // control channel (invoke)
	        	packet.HeaderType=HeaderType.Large;
	        	packet.PacketType= PacketType.SharedObject;
	            packet.AllocPacket(4096);
	            Array.Copy(enc.ToArray(), packet.m_body, enc.Count);
	            packet.m_nBodySize = (uint)enc.Count;
	            con.SendPacket(packet,true);
			}
			catch{return false;}
			return true;
		}
		private int handleStatus(byte[] data,int offset,int size)
		{
			string message=Coding.ReadString(data,offset);
			int msgsize=Coding.ReadInt16(data,offset);
			string status=Coding.ReadString(data,offset+msgsize+2);
			int statussize=Coding.ReadInt16(data,offset+msgsize+2);
			onStatus(status,message);
			logger.Log("SO: "+name+"--->Status message arrived: "+status+": "+message);
			return msgsize+statussize+4;
		}
		private object insertAMFObject(AMFObject prop)
		{
			object pdata=null;
			foreach(AMFObjectProperty p in prop.m_properties)
			{
				object obj=null;
				if(p.m_type==AMFDataType.AMF_NUMBER)
					obj=p.m_dNumVal;
				else if(p.m_type==AMFDataType.AMF_BOOLEAN)
				{
					if(p.m_dNumVal==0) obj=false;
					else obj=true;
				}
				else if(p.m_type== AMFDataType.AMF_STRING)
					obj=p.m_strVal;
				else if(p.m_type==AMFDataType.AMF_STRICT_ARRAY || p.m_type==AMFDataType.AMF_OBJECT || 
				        p.m_type==AMFDataType.AMF_TYPED_OBJECT)
					obj=insertAMFObject(p.m_objVal);
				else
				{
					logger.Log("Unsupported shared object type");
					continue;
				}
				if(pdata==null)
				{
					if(p.m_strName.Length>0)
						pdata=new Dictionary<object, object>();
					else
						pdata=new List<object>();
				}
				if(pdata.GetType()==typeof(List<object>))
					((List<object>)pdata).Add(obj);
				else
					((Dictionary<object, object>)pdata).Add(p.m_strName,obj);
			}
			return pdata;
		}
		private void encodeObject(ref List<byte>data,object amfdata)
		{
			List<byte>temp=new List<byte>();
			byte[] bcount=null;
			if(amfdata.GetType()==typeof(List<object>))
			{
				byte[] btemp=BitConverter.GetBytes(((List<object>)amfdata).Count);
				bcount=new Byte[]{btemp[3],btemp[2],btemp[1],btemp[0]};
				foreach(object obj in (List<object>)amfdata)
				{
					try
					{
						if(obj.GetType()==typeof(bool))
							encodeBoolean(temp,(bool)obj);
						else if(obj.GetType()==typeof(double))
							encodeNumber(temp,(double)obj);
						else if(obj.GetType()==typeof(string))
							encodeString(temp,(string)obj);
						else if(obj.GetType()==typeof(List<object>) || obj.GetType()==typeof(Dictionary<object,object>))
							encodeObject(ref data,obj);
						else
							continue;
					}
					catch{continue;}
				}
				data.Add((byte)AMFDataType.AMF_STRICT_ARRAY);
			}
			else if(amfdata.GetType()==typeof(Dictionary<object, object>))
			{
				byte[] btemp=BitConverter.GetBytes(((Dictionary<object, object>)amfdata).Keys.Count);
				bcount=new Byte[]{10,btemp[3],btemp[2],btemp[1],btemp[0]};
				foreach(object nameobj in ((Dictionary<object, object>)amfdata).Keys)
				{
					try
					{
						object obj=null;
						encodeName(temp,(string)nameobj);
						if(!((Dictionary<object, object>)amfdata).TryGetValue(nameobj,out obj))
							continue;
						if(obj.GetType()==typeof(bool))
							encodeBoolean(temp,(bool)obj);
						else if(obj.GetType()==typeof(double))
							encodeNumber(temp,(double)obj);
						else if(obj.GetType()==typeof(string))
							encodeString(temp,(string)obj);
						else if(obj.GetType()==typeof(List<object>) || obj.GetType()==typeof(Dictionary<object,object>))
							encodeObject(ref data,obj);
						else
							continue;
						
					}
					catch{continue;}
				}
				data.Add((byte)AMFDataType.AMF_ECMA_ARRAY);
			}
			data.AddRange(bcount);
			data.AddRange(temp);
		}
		private bool deleteData(string name)
		{
			if(data==null)
				return false;
			if(data.GetType()!=typeof(Dictionary<object, object>))
				return false;
			Dictionary<object, object>dic=(Dictionary<object, object>)data;
			if(dic.Remove((object)name))
				return true;
			return false;
		}
		private int insertData(byte[] data,int offset,int size)
		{
			int pos=0;
			AMFObjectProperty prop=new AMFObjectProperty();
			pos+=prop.Decode(data,offset,size,true);
			object obj=null;
			if(pos==-1)
				return size;
			if(prop.m_type==AMFDataType.AMF_NUMBER)
				obj=prop.m_dNumVal;
			else if(prop.m_type==AMFDataType.AMF_BOOLEAN)
			{
				if(prop.m_dNumVal==0)
					obj=false;
				else
					obj=true;
			}
			else if(prop.m_type== AMFDataType.AMF_STRING)
				obj=prop.m_strVal;
			else if(prop.m_type==AMFDataType.AMF_STRICT_ARRAY || prop.m_type==AMFDataType.AMF_OBJECT || 
			        prop.m_type==AMFDataType.AMF_TYPED_OBJECT)
				obj=insertAMFObject(prop.m_objVal);
			else
			{
				logger.Log("Unsupported shared object type");
				return size;
			}
			if(this.data==null)
			{
				if(prop.m_strName.Length==0)
					this.data=new List<object>();
				else
					this.data=new Dictionary<object, object>();
			}			
			if(this.data.GetType()==typeof(List<object>))
				((List<object>)this.data).Add(obj);
			else
			{
				Dictionary<object, object>sodata=(Dictionary<object, object>)this.data;
				if(!initrunning)
				{
					if(sodata.ContainsKey(prop.m_strName))
						onSync(prop.m_strName,sodata[prop.m_strName],obj);
					else
						onSync(prop.m_strName,null,obj);
				}
				if(sodata.ContainsKey(prop.m_strName))
					deleteData(prop.m_strName);
				sodata.Add(prop.m_strName,obj);
			}
			return pos;
		}
		private int handleUpdateData(byte[] data,int offset,int size)
		{
			int pos=0;
			while(pos<size)
			{
				pos+=insertData(data,offset,size);
			}
			return pos;
		}
		private int handleAcceptRequest(byte[] data, int offset,int size)
		{
			int pos=0;
			while(pos<size)
			{
				string name=Coding.ReadString(data,offset+pos);
				object newData=null;
				if(PendingData.TryGetValue((object)name,out newData))
				{
					object oldData=null;
					((Dictionary<object, object>)this.data).TryGetValue((object)name,out oldData);
					onSync(name,oldData,newData);
					Dictionary<object, object>dic=(Dictionary<object, object>)this.data;
					if(dic.ContainsKey((object)name))
						dic[(object)name]=newData;
					else
						dic.Add((object)name,newData);
					PendingData.Remove((object)name);
				}
				pos+=name.Length+2;
				logger.Log("SO "+name+": ---> Update data accepted on property: "+name);
			}
			return pos;
		}
		
		private int handleDeleteData(byte[] data,int offset,int size)
		{
			int pos=0;
			while(pos<size)
			{
				AMFObjectProperty prop=new AMFObjectProperty();
				prop.Decode(data,offset,size,true);
				pos+=prop.m_strName.Length+2;
				if(prop.m_strName.Length>0)
				{
					Dictionary<object, object>sodata=(Dictionary<object, object>)this.data;
					if(!sodata.ContainsKey(prop.m_strName))
						continue;
					onSync(prop.m_strName,sodata[prop.m_strName],null);
					if(deleteData(prop.m_strName))
					{}
						//logger.Log("SO "+name+": ---> Property: "+prop.m_strName+" deleted");
				}
			}
			return pos;
		}
		private int handleInitData(byte[] data, int offset,int size)
		{
			int pos=0;
			logger.Log("SO: "+name+"---> Connect success message arrived!");
			while(pos<size)
			{
				AMFObjectProperty prop=new AMFObjectProperty();
				pos+=prop.Decode(data,offset,size,true);
			}
			return pos;
		}
		private int handleClear(byte[] data,int offset,int size)
		{
			this.data=null;
			logger.Log("SO: "+name+"---> Clear request arrived!");
			return 0;
		}
		private int handleSendMessage(byte[] data,int offset,int size)
		{
			if(client==null)
				return size;
			int pos=0;
			AMFObject obj=new AMFObject();
			pos+=obj.Decode(data,offset,size,false);
			try
			{
				string methodname=obj.GetProperty(0).GetString();
				Type t=client.GetType();
				if(t==null)
					throw new Exception();
				MethodInfo method=t.GetMethod(methodname);
				if(method==null)
					return size;
				if(obj.m_properties.Count>1)
				{
					List<object>param=new List<object>();
					for(int i=1;i<obj.m_properties.Count;i++)
					{
						AMFObjectProperty prop=obj.m_properties[i];
						if(prop.m_type==AMFDataType.AMF_BOOLEAN || prop.m_type== AMFDataType.AMF_NUMBER)
							param.Add((object)prop.m_dNumVal);
						else if(prop.m_type==AMFDataType.AMF_STRING)
							param.Add((object)prop.m_strVal);
						else if(prop.m_objVal!=null)
							param.Add((object)prop.m_objVal);
					}
					method.Invoke(client,param.ToArray());
				}
				else
					method.Invoke(client,null);
						              
			}
			catch{}
			return size;
		}
		private int handleEvent(byte[] data,int offset)
		{
			int type=(int)data[offset];
			int size=(int)Coding.ReadInt32(data,offset+1);
			int dataoffset=offset+5;
			int pos=size;
			switch(type)
		    {
				case (int)SOEvents.Change:
					pos=handleUpdateData(data,dataoffset,size);
					//logger.Log("-------------Shared object: "+name+"----------------");
					//dump(this.data);
					break;
				case (int)SOEvents.Success:
					pos=handleAcceptRequest(data,dataoffset,size);
					//logger.Log("-------------Shared object: "+name+"----------------");
					//dump(this.data);
					break;
				case (int)SOEvents.SendMessage:
					pos=handleSendMessage(data,dataoffset,size);
					break;
				case (int)SOEvents.Status: 
					pos=handleStatus(data,dataoffset,size);
		    		break;
		    	case (int)SOEvents.Clear: 
		    		pos=handleClear(data,dataoffset,size);
		    		onCleared();
		    		break;
		    	case (int)SOEvents.Remove: 
		    		pos=handleDeleteData(data,dataoffset,size);
					//logger.Log("-------------Shared object: "+name+"----------------");
					//dump(this.data);
		    		break;
		    	case (int)SOEvents.UseSuccess: 
		    		IsConnected=true;
		    		initrunning=true;
		    		pos=handleInitData(data,dataoffset,size);
		    		break;
		    	default:
		    		logger.Log("Unknown shared object event arrived: "+type.ToString());
		    		break;
		    }
			return 5+pos;
		}
		internal void handleEvents(byte[] data)
		{
			int offset=0;
			int count=0;
			while(offset<data.Length && count<5000)
			{
				offset+=handleEvent(data,offset);
				count++;
			}
			if(initrunning)
			{
				initrunning=false;
				//dump(this.data);
				onConnected();
			}
			if(count==4999 && offset<data.Length)
				logger.Log("SharedObject<"+name+ "> error: event overflow");
		}
        private void encodeBoolean(List<byte> output, bool bVal)
        {
            output.Add(0x1); // type: Boolean
            output.Add(bVal ? (byte)0x1 : (byte)0x0);
        }
		private void encodeName(List<byte>output,string strValue)
		{
            short length = IPAddress.HostToNetworkOrder((short)strValue.Length);
            output.AddRange(BitConverter.GetBytes(length));
            output.AddRange(Encoding.ASCII.GetBytes(strValue));
		}
		private void encodeString(List<byte>output,string strValue)
		{
            output.Add(0x2); // type: Number
            short length = IPAddress.HostToNetworkOrder((short)strValue.Length);
            output.AddRange(BitConverter.GetBytes(length));
            output.AddRange(Encoding.ASCII.GetBytes(strValue));
		}
        private void encodeNumber(List<byte> output, double dVal)
        {
            output.Add(0x0); // type: Number
            byte[] bytes = BitConverter.GetBytes(dVal);
            for (int i = bytes.Length - 1; i >= 0; i--) output.Add(bytes[i]); // add in reversed byte order
        }
		internal void dump(object data)
		{
			if(data==null)
				return;
			if(data.GetType()==typeof(List<object>))
			{
				foreach(object obj in ((List<object>)data))
				{
					try
					{
						if(obj.GetType()!=typeof(List<object>) && obj.GetType()!=typeof(Dictionary<object, object>))
							logger.Log(obj.ToString());
						else
						{
							logger.Log("-------------");
							dump(obj);
							logger.Log("-------------");
						}
					}
					catch(Exception e)
					{
						logger.Log(e.Message);
					}
				}
			}
			else
			{
				foreach(object obj in ((Dictionary<object,object>)data).Keys)
				{
					try
					{
						object d=((Dictionary<object, object>)data)[obj];
						if(d.GetType()!=typeof(List<object>) && d.GetType()!=typeof(Dictionary<object, object>))
							logger.Log(obj.ToString()+": "+d.ToString());
						else
						{
							logger.Log("-------------");
							logger.Log(obj.ToString());
							logger.Log("-------------");
							dump(d);
							logger.Log("-------------");
							logger.Log("             ");
						}
					}
					catch(Exception e)
					{
						logger.Log(e.Message);
					}
				}
			}
		}
		private void onSync(string name,object oldData,object newData)
		{
			if(Sync!=null)
				Sync(name,oldData,newData);
		}
		private void onConnected()
		{
			if(Connected!=null)
				Connected();
		}
		private void onStatus(string status,string message)
		{
			if(Status!=null)
				Status(status,message);
		}
		private void onCleared()
		{
			if(Cleared!=null)
				Cleared();
		}
	}
}










